org 0100h

BaseOfStack equ 0100h
PageDirBase	equ	100000h	; 页目录开始地址: 1M
PageTblBase	equ	101000h	; 页表开始地址:   1M + 4K



%include "common_head.asm"
%include "pm.inc"

BaseOfLoadFile equ BaseOfKernelFile
OffsetOfLoadFile equ OffsetOfKernelFile

; 这里和于渊老师的代码不一样，于渊老师在这里有个jmp LABEL_START，并且也把loader做成了可以FAT启动盘一样的格式，我认为没必要
; 所以这里直接从mov ax, cs开始
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack

mov dh, 3
push LoadMessage
call DispStrRealMode
add sp, 2

; 得到内存数
	mov	ebx, 0			; ebx = 后续值, 开始时需为 0
	mov	di, _MemChkBuf		; es:di 指向一个地址范围描述符结构(ARDS)
.MemChkLoop:
	mov	eax, 0E820h		; eax = 0000E820h
	mov	ecx, 20			; ecx = 地址范围描述符结构的大小
	mov	edx, 0534D4150h		; edx = 'SMAP'
	int	15h			; int 15h
	jc	.MemChkFail
	add	di, 20
	inc	dword [_dwMCRNumber]	; dwMCRNumber = ARDS 的个数
	cmp	ebx, 0
	jne	.MemChkLoop
	jmp	.MemChkOK
.MemChkFail:
	mov	dword [_dwMCRNumber], 0
.MemChkOK:

xor ah, ah
xor dl, dl
int 13h

push KernelFileName
call findInRootDir
add sp, 2

cmp ax, 0
jz kernel_found
kernel_not_found:
	; 没有找到内核，留后处理
	mov dh, 4
	push Message2
	call DispStrRealMode
	add sp, 2
	jmp $
kernel_found:
	and di, 0FFE0h
	add di, 01Ah
	
	mov eax, dword [es:di + 01Ch]
	mov dword [dwKernelSize], eax
	
	mov ax, [es:di]
	push ax
	call ReadFileByFirstSector
	add sp, 2
	cmp ax, 0
	jz loadSuccess
	; 如果加载失败，以后再处理
	jmp $
loadSuccess:
	call KillMotor
	mov dh, 4
	push Message1
	call DispStrRealMode
	add sp, 2
	jmp EnterProtectedMode
	
LABEL_GDT:
	Descriptor 0, 0, 0 ; 空描述符
	LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K ; 0~4G代码段
	LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K ; 0~4G数据段
	LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW | DA_32 ; 跟于渊老师的不一样，我定义了一个数据段，这样访问数据会方便一些
	LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32
	LABEL_DESC_VIDEO: Descriptor 0B8000h, 0fffffh, DA_DRW | DA_DPL3 ; 显存段
	
	GdtLen equ $ - LABEL_GDT
	GdtPtr dw GdtLen - 1
		dd BaseOfLoaderPhyAddr + LABEL_GDT
		
SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT
SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
	
EnterProtectedMode:
	xor eax, eax
	; 这里ds和cs一样都是09000h
	mov ax, ds
	shl eax, 4
	add eax, LABEL_DATA
	mov word [LABEL_DESC_DATA + 2], ax
	shr eax, 16
	mov byte [LABEL_DESC_DATA + 4], al
	mov byte [LABEL_DESC_DATA + 7], ah
	
	xor eax, eax
	mov ax, ds
	shl eax, 4
	add eax, LABEL_STACK
	mov word [LABEL_DESC_STACK + 2], ax
	shr eax, 16
	mov byte [LABEL_DESC_STACK + 4], al
	mov byte [LABEL_DESC_STACK + 7], ah
	
	; 加载全局描述表
	lgdt [GdtPtr]
	
	; 关中断
	cli
	
	; 打开A20地址线
	in al, 92h
	or al, 00000010b
	out 92h, al
	
	; 切换到保护模式
	mov eax, cr0
	or eax, 1
	mov cr0, eax
	
	; 真正跳入保护模式32位代码段
	jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr + LABEL_PM_START)

wSectorNo dw 0
bOdd db 0
dwKernelSize dd 0

KernelFileName db "KERNEL  BIN"
LoadMessage: db "Loading  "
Message1 db "Ready.   "
Message2 db "No KERNEL"

%include "common_funcs.asm"

KillMotor:
	push dx
	mov dx, 03F2h
	mov al, 0
	out dx, al
	pop dx
	ret
	
[SECTION .s32]
ALIGN 32
[BITS 32]
LABEL_PM_START:
	mov ax, SelectorVideo
	mov gs, ax
	
	; 这里数据段用我们定义的数据段，然后在计算偏移量的时候，就不需要加段基址的物理地址了。
	mov ax, SelectorData
	mov ds, ax
	mov ax, SelectorFlatRW
	mov es, ax
	mov fs, ax
	mov ax, SelectorStack
	mov ss, ax
	mov esp, TopOfStack
	
	push szMemChkTitle
	call DispStr
	add esp, 4
	
	call DispMemInfo
	call SetupPaging
	
	DispCharColor 0, 39, 'P', 0Fh
	jmp $
	
%include "lib.inc"

; 显示内存信息 --------------------------------------------------------------
DispMemInfo:
	push	esi
	push	edi
	push	ecx
	push es

	mov ax, SelectorData
	mov es, ax
	mov	esi, MemChkBuf
	mov	ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到一个ARDS
.loop:				  ;{
	mov	edx, 5		  ;  for(int j=0;j<5;j++)//每次得到一个ARDS中的成员
	mov	edi, ARDStruct	  ;  {//依次显示:BaseAddrLow,BaseAddrHigh,LengthLow
.1:				  ;               LengthHigh,Type
	push	dword [esi]	  ;
	call	DispInt		  ;    DispInt(MemChkBuf[j*4]); // 显示一个成员
	pop	eax		  ;
	stosd			  ;    ARDStruct[j*4] = MemChkBuf[j*4];
	add	esi, 4		  ;
	dec	edx		  ;
	cmp	edx, 0		  ;
	jnz	.1		  ;  }
	call	DispReturn	  ;  printf("\n");
	cmp	dword [dwType], 1 ;  if(Type == AddressRangeMemory)
	jne	.2		  ;  {
	mov	eax, [dwBaseAddrLow];
	add	eax, [dwLengthLow];
	cmp	eax, [dwMemSize]  ;    if(BaseAddrLow + LengthLow > MemSize)
	jb	.2		  ;
	mov	[dwMemSize], eax  ;    MemSize = BaseAddrLow + LengthLow;
.2:				  ;  }
	loop	.loop		  ;}
				  ;
	call	DispReturn	  ;printf("\n");
	push	szRAMSize	  ;
	call	DispStr		  ;printf("RAM size:");
	add	esp, 4		  ;
				  ;
	push	dword [dwMemSize] ;
	call	DispInt		  ;DispInt(MemSize);
	add	esp, 4		  ;

	pop es
	pop	ecx
	pop	edi
	pop	esi
	ret
; ---------------------------------------------------------------------------

; 启动分页机制 --------------------------------------------------------------
SetupPaging:
	; 根据内存大小计算应初始化多少PDE以及多少页表
	xor	edx, edx
	mov	eax, [dwMemSize]
	mov	ebx, 400000h	; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
	div	ebx
	mov	ecx, eax	; 此时 ecx 为页表的个数，也即 PDE 应该的个数
	test	edx, edx
	jz	.no_remainder
	inc	ecx		; 如果余数不为 0 就需增加一个页表
.no_remainder:
	push	ecx		; 暂存页表个数

	; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.

	; 首先初始化页目录
	mov	ax, SelectorFlatRW
	mov	es, ax
	mov	edi, PageDirBase	; 此段首地址为 PageDirBase
	xor	eax, eax
	mov	eax, PageTblBase | PG_P  | PG_USU | PG_RWW
.1:
	stosd
	add	eax, 4096		; 为了简化, 所有页表在内存中是连续的
	loop	.1

	; 再初始化所有页表
	pop	eax			; 页表个数
	mov	ebx, 1024		; 每个页表 1024 个 PTE
	mul	ebx
	mov	ecx, eax		; PTE个数 = 页表个数 * 1024
	mov	edi, PageTblBase	; 此段首地址为 PageTblBase
	xor	eax, eax
	mov	eax, PG_P  | PG_USU | PG_RWW
.2:
	stosd
	add	eax, 4096		; 每一页指向 4K 的空间
	loop	.2

	mov	eax, PageDirBase
	mov	cr3, eax
	mov	eax, cr0
	or	eax, 80000000h
	mov	cr0, eax
	jmp	short .3
.3:
	nop

	ret
; 分页机制启动完毕 ----------------------------------------------------------

	
; 保护模式下的数据段
[SECTION .data1]
ALIGN 32
LABEL_DATA:
; 实模式下使用这些符号
; 字符串
_szMemChkTitle:	db "BaseAddrL BaseAddrH LengthLow LengthHigh   Type", 0Ah, 0
_szRAMSize:	db "RAM size:", 0
_szReturn:	db 0Ah, 0
;; 变量
_dwMCRNumber:	dd 0	; Memory Check Result
_dwDispPos:	dd (80 * 6 + 0) * 2	; 屏幕第 6 行, 第 0 列
_dwMemSize:	dd 0
_ARDStruct:	; Address Range Descriptor Structure
  _dwBaseAddrLow:		dd	0
  _dwBaseAddrHigh:		dd	0
  _dwLengthLow:			dd	0
  _dwLengthHigh:		dd	0
  _dwType:			dd	0
_MemChkBuf:	times	256	db	0
;
;; 保护模式下使用这些符号
szMemChkTitle		equ	_szMemChkTitle - LABEL_DATA
szRAMSize		equ	_szRAMSize - LABEL_DATA
szReturn		equ	_szReturn - LABEL_DATA
dwDispPos		equ	_dwDispPos - LABEL_DATA
dwMemSize		equ	_dwMemSize - LABEL_DATA
dwMCRNumber		equ	_dwMCRNumber - LABEL_DATA
ARDStruct		equ	_ARDStruct - LABEL_DATA
	dwBaseAddrLow	equ	_dwBaseAddrLow - LABEL_DATA
	dwBaseAddrHigh	equ	_dwBaseAddrHigh - LABEL_DATA
	dwLengthLow	equ	_dwLengthLow - LABEL_DATA
	dwLengthHigh	equ	_dwLengthHigh - LABEL_DATA
	dwType		equ	_dwType - LABEL_DATA
MemChkBuf		equ	_MemChkBuf - LABEL_DATA
DataLen equ $ - LABEL_DATA
; 

; 堆栈段，这里也和于渊老师的代码不一样
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
	times 1024 db 0
TopOfStack equ $ - LABEL_STACK - 1